﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Linq;
using System.Resources;

using UnityEngine.SceneManagement;
#if AT_STREAMER2
using Atavism;
#endif

namespace XYTerrain.Runtime
{
    /// <summary>
    /// Streams async scene tiles
    /// </summary
#if AT_STREAMER2
    public class Streamer : AtavismStreamer
#else
    public class Streamer : MonoBehaviour
#endif
    {



        /// <summary>
        /// Activates/deactivates streamer.
        /// </summary>
        [Tooltip("This checkbox deactivates streamer and unload or doesn't load it's data.")]
        public bool streamerActive = true;

        /// <summary>
        /// The streamer tag.
        /// </summary>
        public static string STREAMERTAG = "SceneStreamer";
        // [HideInInspector]
        [Tooltip("Drag and drop here your scene collection prefab. You could find it in catalogue with scenes which were generated by scene splitter.")]
        /// <summary>
        /// The scene collection of tiles.
        /// </summary>
        public List<SceneCollectionManager> sceneCollectionManagers;

        [Header("Settings")]
        [Tooltip("Frequancy in seconds in which you want to check if grid element is close /far enough to load/unload.")]
        /// <summary>
        /// How often streamer checks player position.
        /// </summary>
        public float positionCheckTime = 0.1f;
        [Tooltip("Time in seconds after which grid element will be unloaded.")]
        /// <summary>
        /// Destroys unloaded tiles after seconds.
        /// </summary>
        public float destroyTileDelay = 2;

        [Tooltip("Number of empty frames between loading actions.")]
        /// <summary>
        /// The async scene load wait frames.
        /// </summary>
        public int sceneLoadWaitFrames = 2;

        [Space(10)]
        [Tooltip("If you want to fix small holes from LODs system at unity terrain borders, drag and drop object here from scene hierarchy that contains our \"Terrain Neighbours\" script.")]
        /// <summary>
        /// The terrain neighbours manager.
        /// </summary>
        public TerrainNeighbours terrainNeighbours;


        [Space(10)]
        [Tooltip("Enable looping system, each layer is streamed independently, so if you want to synchronize them, they should have the same XYZ size. More info at manual.")]
        /// <summary>
        /// Is world looping on.
        /// </summary>
        public bool looping = false;

       // Add By Dk   2022 - 09 - 01
       //添加玩家Tile信息更变回调
       public Action<string> OnPlayerTileChange;
        
        

       //--------------------------------changeBy Dk 2022-07-26--------------------- 
       // overrangeLimit 应当与Looping进行联合判断 否则会导致加载无故使用循环加载.
        
        /// <summary>
        /// Override scene split range limit
        /// </summary>
        public bool overideRangeLimit
        {
            get
            {
                return _overideRangeLimit && looping;
            }
            set
            {
                _overideRangeLimit = value;
            }
        }

        public bool _overideRangeLimit = true;

        /// <summary>
        /// Override scene limits in scene collections
        /// </summary>
        public bool overideScenesLimits
        {
            get => _overideScenesLimits && looping;
            set
            {
                _overideScenesLimits = value;
            }
        }

        public bool _overideScenesLimits = true;
        
        //--------------------------------------EndChange-----------------------------------
        
        
        [Space(10)]
        [Header("Player Settings")]
        [Tooltip("Drag and drop here, an object that system have to follow during streaming process.")]
        /// <summary>
        /// The player transform.
        /// </summary>
        [SerializeField]
        private Transform _player;

        public Transform player
        {
            get
            {
                if (spawnedPlayer && _player == null && !string.IsNullOrEmpty(playerTag))
                {
                    GameObject playerGO = GameObject.FindGameObjectWithTag(playerTag);
                    if (playerGO != null)
                        _player = playerGO.transform;
                }
                return _player;
            }
            set => _player = value;
        }

        [Tooltip("Streamer will wait for player spawn and fill it automatically")]
        /// <summary>
        /// Streamer will wait for player spawn and fill it automatically
        /// </summary>
        public bool spawnedPlayer;

        [HideInInspector]
        public string playerTag = "Player";

        [HideInInspector]
        /// <summary>
        /// The show loading screen on start?
        /// </summary>
        public bool showLoadingScreen = true;


        [HideInInspector]
        /// <summary>
        /// The loading screen UI.
        /// </summary>
        public UILoadingStreamer loadingStreamer;

        [HideInInspector]
        public bool initialized = false;

        [HideInInspector]
        /// <summary>
        /// The tiles to load.
        /// </summary>
        public int tilesToLoad = int.MaxValue;

        [HideInInspector]
        /// <summary>
        /// The tiles loaded.
        /// </summary>
        public int tilesLoaded;

        /// <summary>
        /// Gets the loading progress.
        /// </summary>
        /// <value>The loading progress.</value>
        public float LoadingProgress
        {
            get { return (tilesToLoad > 0) ? tilesLoaded / (float)tilesToLoad : 1; }
        }

        /// <summary>
        /// The world mover.
        /// </summary>
        [HideInInspector]
        public WorldMover
            worldMover;

        [HideInInspector]
        /// <summary>
        /// The current move.
        /// </summary>
        public Vector3 currentMove = Vector3.zero;



        /// <summary>
        /// The scenes to load.
        /// </summary>
        List<SceneSplit> scenesToLoad = new List<SceneSplit>();

        /// <summary>
        /// The scene load frame next.
        /// </summary>
        int sceneLoadFrameNext = 0;

        /// <summary>
        /// The scene load frames next waited.
        /// </summary>
        bool sceneLoadFramesNextWaited = false;




        public StreamerLoadingManager loadingManager;

        static bool canUnload = true;
        static float waitTillNextUnload = 20;
        static bool unloadNext = false;

        /// <summary>
        /// Awakes this instance and resets player position;
        /// </summary>
        void Awake()
        {
            //if (spawnedPlayer)
            //{
            //    player = null;
            //}
#if AT_STREAMER2
            if (ClientAPI.GetPlayerObject() != null)
            {
                GameObject go = ClientAPI.GetPlayerObject().GameObject;
                if (go != null)
                    player = go.transform;
            }
#endif
            foreach (var sceneCollectionManager in sceneCollectionManagers)
            {
                sceneCollectionManager.ResetPosition();
            }
        }


        /// <summary>
        /// Start this instance, prepares scene collection into scene array, starts player position checker
        /// </summary>
        void Start()
        {
            loadingManager = new StreamerLoadingManager() { streamer = this };



            if (sceneCollectionManagers != null && sceneCollectionManagers.Count > 0)
            {

                PrepareScenesArray();

                StartCoroutine(PositionChecker());
                canUnload = true;
            }
            else
            {
                enabled = false;
                Debug.LogError("No scene collection in streamer");
            }

        }

        #region prepare scene

        /// <summary>
        /// Prepares the scenes array from collection
        /// </summary>
        void PrepareScenesArray()
        {
            Vector2Int xLimits = Vector2Int.zero;
            Vector2Int yLimits = Vector2Int.zero;
            Vector2Int zLimits = Vector2Int.zero;

            if (sceneCollectionManagers.Count > 1)
            {
                sceneCollectionManagers[0].GetSceneCollectionWolrdLimits(out xLimits, out yLimits, out zLimits);
                Debug.Log(xLimits + " " + yLimits + " " + zLimits);
            }
            foreach (var sceneCollectionManager in sceneCollectionManagers)
            {
                sceneCollectionManager.unloadRangeConnectParent = null;
            }
            foreach (var sceneCollectionManager in sceneCollectionManagers)
            {
                if (sceneCollectionManager.useUnloadRangeConnect && sceneCollectionManager.unloadRangeConnect != null)
                {
                    sceneCollectionManager.unloadRangeConnect.unloadRangeConnectParent = sceneCollectionManager;
                }

                if (overideScenesLimits)
                {
                    sceneCollectionManager.CalculateLoadingLimits(looping, overideRangeLimit, overideScenesLimits, xLimits, yLimits, zLimits);
                    sceneCollectionManager.PrepareScenesArray(overideRangeLimit, overideScenesLimits, xLimits, yLimits, zLimits);
                }
                else
                {

                    sceneCollectionManager.CalculateLoadingLimits(looping, overideRangeLimit);
                    sceneCollectionManager.PrepareScenesArray(overideRangeLimit);
                }
            }
        }

        /// <summary>
        /// Converts scene name into position
        /// </summary>
        /// <param name="sceneName">Scene name.</param>
        /// <param name="posX">Position x.</param>
        /// <param name="posY">Position y.</param>
        /// <param name="posZ">Position z.</param>
        public static void SceneNameToPos(SceneCollectionManager sceneCollectionManager, string sceneName, out int posX, out int posY, out int posZ)
        {
            posX = 0;
            posY = 0;
            posZ = 0;

            string[] values = sceneName.Replace(sceneCollectionManager.prefixScene, "").Replace(".unity", "").Split(new char[] {
            '_'
        }, System.StringSplitOptions.RemoveEmptyEntries);

            foreach (var item in values)
            {
                if (item[0] == 'x')
                {
                    posX = int.Parse(item.Replace("x", ""));
                }
                if (item[0] == 'y')
                {
                    posY = int.Parse(item.Replace("y", ""));
                }
                if (item[0] == 'z')
                {
                    posZ = int.Parse(item.Replace("z", ""));
                }
            }

        }





        #endregion

        public static int mod(int x, int m)
        {
            return (x % m + m) % m;
        }

        #region scene loading
        // called second
        public void OnSceneLoaded(Scene scene, SceneSplit split)
        {

            GameObject[] rootGameObjects = scene.GetRootGameObjects();

            if (rootGameObjects.Length > 0)
            {

                AddSceneGOMin(split, rootGameObjects[0]);
            }

        }

        public void AddSceneGOMin(SceneSplit split, GameObject sceneGO)
        {

            SceneCollectionManager sceneCollectionManager = split.sceneCollectionManager;
            int posX = 0;
            int posY = 0;
            int posZ = 0;

            SceneNameToPos(split.sceneCollectionManager, split.sceneName, out posX, out posY, out posZ);
            Vector3Int posInt = new Vector3Int(posX, posY, posZ);


            SceneSplitManager sceneSplitManager = sceneGO.GetComponent<SceneSplitManager>();

            tilesLoaded++;

            float x = split.posX - posInt.x;
            float y = split.posY - posInt.y;
            float z = split.posZ - posInt.z;

            Vector3 posSplit = new Vector3(sceneCollectionManager.xSize * x, sceneCollectionManager.ySize * y, sceneCollectionManager.zSize * z) + currentMove + new Vector3(split.posXLimitMove, split.posYLimitMove, split.posZLimitMove);


            // Debug.Log("Added " + posInt + " " + posIntMoved);
            split.sceneGo = sceneGO;
            split.scene = sceneGO.scene;
            split.loadingFinished = true;

            split.sceneCollectionManager.currentlySceneLoading--;
            sceneGO.transform.position = posSplit;



            SceneCollectionManager unloadSceneCollectionManager = null;
            if (sceneCollectionManager.useUnloadRangeConnect && sceneCollectionManager.unloadRangeConnect != null)
            {
                unloadSceneCollectionManager = sceneCollectionManager.unloadRangeConnect;
            }
            if (sceneCollectionManager.unloadRangeConnectParent != null && sceneCollectionManager.unloadRangeConnectParent.useUnloadRangeConnect)
            {
                unloadSceneCollectionManager = sceneCollectionManager.unloadRangeConnectParent;
            }


            if (unloadSceneCollectionManager != null)
            {
                SceneSplit splitCheck;
                float count = unloadSceneCollectionManager.loadedScenes.Count;
                for (int i = 0; i < count; i++)
                {
                    splitCheck = unloadSceneCollectionManager.loadedScenes[i];

                    if (splitCheck.loadingFinished)
                    {

                        if (split.basePosX == splitCheck.basePosX && split.basePosY == splitCheck.basePosY && split.basePosZ == splitCheck.basePosZ)
                        {

                            if (posSplit == splitCheck.sceneGo.transform.position)
                            {
                                //Debug.Log("----Unloaded " + split.sceneName);


                                UnloadScenesSync(splitCheck);
                                break;


                            }

                        }
                    }

                }
            }


        }


        #endregion

        #region update functions

        /// <summary>
        /// Update this instance, starts load level async
        /// </summary>
        void Update()
        {
            LoadLevelAsyncManage();
            loadingManager.Update();
        }

        /// <summary>
        /// Manages async scene loading
        /// </summary>
        void LoadLevelAsyncManage()
        {


            //Debug.Log("--------------------------------" + scenesToLoad.Count);

            if (scenesToLoad.Count > 0)
            {

                if (LoadingProgress < 1 || sceneLoadFramesNextWaited && sceneLoadFrameNext <= 0)
                {

                    //Debug.Log(LoadingProgress);
                    sceneLoadFramesNextWaited = false;
                    sceneLoadFrameNext = sceneLoadWaitFrames;

                    scenesToLoad = scenesToLoad.OrderBy(x => x.sceneCollectionManager.priority).ToList();


                    int i = 0;
                    while (scenesToLoad.Count > 0 && i < scenesToLoad.Count)
                    {
                        SceneSplit split = scenesToLoad[i];

                        
                        if (split.sceneCollectionManager.currentlySceneLoading < split.sceneCollectionManager.maxParallelSceneLoading)
                        {
                            //Debug.Log(split.sceneName);
                            scenesToLoad.Remove(split);
                            split.sceneCollectionManager.currentlySceneLoading++;
                            loadingManager.LoadSceneAsync(split);
                            i--;
                        }
                        i++;


                    }
                }
                else
                {
                    sceneLoadFramesNextWaited = true;
                    sceneLoadFrameNext--;
                }
            }
        }

        /// <summary>
        /// Coroutine checks player position
        /// </summary>
        /// <returns>The checker.</returns>
        IEnumerator PositionChecker()
        {
            while (true)
            {

                if (spawnedPlayer && player == null && !string.IsNullOrEmpty(playerTag))
                {
                    GameObject playerGO = GameObject.FindGameObjectWithTag(playerTag);
#if AT_STREAMER2
                    if(ClientAPI.GetPlayerObject() != null && playerGO==null)
                        playerGO =  ClientAPI.GetPlayerObject().GameObject; 
#endif
                    if (playerGO != null)
                        player = playerGO.transform;
                }


                if (streamerActive && player != null)
                {
                    CheckPositionTiles();
                }
                else
                {
#if !AT_STREAMER2
                    bool loadedScenes = false;

                    foreach (var sceneCollectionManager in sceneCollectionManagers)
                    {
                        if (sceneCollectionManager.loadedScenes.Count > 0)
                        {

                            loadedScenes = true;
                            sceneCollectionManager.ResetPosition();
                        }
                    }
                    if (loadedScenes)
                        UnloadAllScenes();
#endif
                }


                yield return new WaitForSeconds(positionCheckTime);
            }
        }

        /// <summary>
        /// Checks the position of player in tiles.
        /// </summary>
        public void CheckPositionTiles()
        {
            Vector3 pos = player.position;

            pos -= currentMove;

            bool changed = false;

            foreach (var sceneCollectionManager in sceneCollectionManagers)
            {
                if (sceneCollectionManager.CheckPosition(pos))
                {
                    string name = $"{sceneCollectionManager.prefixName}_x{sceneCollectionManager.xPos}_z{sceneCollectionManager.zPos}";
                    #if  _STREAMER_DEBUG
                    Debug.Log($"TileChange : {name}");
                    #endif
                    OnPlayerTileChange?.Invoke(name);    
                    changed = true;
                }
            }

            if (changed)
            {
                SceneLoading();
                Invoke("SceneUnloading", destroyTileDelay);

                if (worldMover != null)
                {
                    worldMover.CheckMoverDistance(sceneCollectionManagers[0].xPos, sceneCollectionManagers[0].yPos, sceneCollectionManagers[0].zPos);
                }
            }
        }

        #endregion

        #region loading and unloading


        /// <summary>
        /// Loads tiles in range
        /// </summary>
        void SceneLoading()
        {
            //show splash screen
            if (showLoadingScreen)
            {
                showLoadingScreen = false;
                if (tilesLoaded >= tilesToLoad)
                {
                    tilesToLoad = int.MaxValue;
                    tilesLoaded = 0;
                }
            }


            int tilesToLoadNew = 0;
            int count = 0;

            foreach (var scm in sceneCollectionManagers)
            {
                if (!scm.active)
                {
                    continue;
                }
                int x = scm.xPos;
                int y = scm.yPos;
                int z = scm.zPos;



                int LoadingRangeX = (int)scm.loadingRange.x * 2 + 1;
                int LoadingRangeZ = (int)scm.loadingRange.z * 2 + 1;
                int xs = 0, zs = 0, dx = 0, dy = -1;
                int t = Math.Max(LoadingRangeX, LoadingRangeZ);
                int maxI = t * t;


                //Debug.Log(LoadingRangeX);
                //Debug.Log(LoadingRangeZ);
                SceneSplit split;
                for (int i = 0; i < maxI; i++)
                {
                    if ((-LoadingRangeX / 2 <= xs) && (xs <= LoadingRangeX / 2) && (-LoadingRangeZ / 2 <= zs) && (zs <= LoadingRangeZ / 2))
                    {
                        for (int ys = 0; ys <= scm.loadingRange.y; ys++)
                        {
                            for (int yi = -1; yi <= 1; yi += 2)
                            {

                                if (ys == 0 && yi != -1)
                                    continue;

                                count++;

                                if (scm.useLoadingRangeMin)
                                    if (xs >= -scm.loadingRangeMin.x && xs <= scm.loadingRangeMin.x &&
                                        ys >= -scm.loadingRangeMin.y && ys <= scm.loadingRangeMin.y &&
                                        zs >= -scm.loadingRangeMin.z && zs <= scm.loadingRangeMin.z)
                                    {
                                        continue;

                                    }

                                x = xs + scm.xPos;
                                y = ys * yi + scm.yPos;
                                z = zs + scm.zPos;




                                Vector3Int sceneID = new Vector3Int(x, y, z);

                                //Debug.Log(sceneID);


                                float xMoveLimit = 0;
                                int xDeloadLimit = 0;

                                float yMoveLimit = 0;
                                int yDeloadLimit = 0;

                                float zMoveLimit = 0;
                                int zDeloadLimit = 0;

                                //set scene possition according to looping
                                if (looping)
                                {

                                    if (scm.xSplitIs)
                                    {

                                        int xFinal = mod((x + Mathf.Abs(scm.xLoadingLimitx)), scm.xLoadingRange) + scm.xLoadingLimitx;

                                        xDeloadLimit = (int)Math.Ceiling((x - scm.xLoadingLimity) / (float)scm.xLoadingRange) * scm.xLoadingRange;
                                        xMoveLimit = xDeloadLimit * scm.xSize;

                                        sceneID[0] = xFinal;
                                    }

                                    if (scm.ySplitIs)
                                    {

                                        int yFinal = mod((y + Mathf.Abs(scm.yLoadingLimitx)), scm.yLoadingRange) + scm.yLoadingLimitx;

                                        yDeloadLimit = (int)Math.Ceiling((y - scm.yLoadingLimity) / (float)scm.yLoadingRange) * scm.yLoadingRange;
                                        yMoveLimit = yDeloadLimit * scm.ySize;
                                        sceneID[1] = yFinal;
                                    }


                                    if (scm.zSplitIs)
                                    {
                                        int zFinal = mod((z + Mathf.Abs(scm.zLoadingLimitx)), scm.zLoadingRange) + scm.zLoadingLimitx;

                                        zDeloadLimit = (int)Math.Ceiling((z - scm.zLoadingLimity) / (float)scm.zLoadingRange) * scm.zLoadingRange;
                                        zMoveLimit = zDeloadLimit * scm.zSize;
                                        sceneID[2] = zFinal;
                                    }

                                }


                                //Debug.Log(sceneID[0] + " " + sceneID[1] + " " + sceneID[2]);

                                //load scene if scene array contains it and set up scene offset position according to looping
                                if (scm.scenesArray.TryGetValue(sceneID, out split))
                                {

                                    if (!split.loaded)
                                    {
                                        split.loaded = true;

                                        split.posXLimitMove = xMoveLimit;
                                        split.xDeloadLimit = xDeloadLimit;

                                        split.posYLimitMove = yMoveLimit;
                                        split.yDeloadLimit = yDeloadLimit;

                                        split.posZLimitMove = zMoveLimit;
                                        split.zDeloadLimit = zDeloadLimit;
                                        // Debug.Log("AddToSceneToLoad" + split.sceneName + "SceneID :" + sceneID[0] + " " + sceneID[1] + " " + sceneID[2]);
                                        scenesToLoad.Add(split);
                                        scm.loadedScenes.Add(split);
                                        tilesToLoadNew++;
                                    }
                                }
                                else
                                {
                                    //Debug.Log(scm.name+ " no scene id " + sceneID);

                                }
                            }
                        }
                    }


                    if ((xs == zs) || ((xs < 0) && (xs == -zs)) || ((xs > 0) && (xs == 1 - zs)))
                    {
                        t = dx; dx = -dy; dy = t;
                    }
                    xs += dx; zs += dy;
                }
            }






            tilesToLoad = tilesToLoadNew;

            initialized = true;

        }

        /// <summary>
        /// Unloads tiles out of range
        /// </summary>
        void SceneUnloading()
        {
            List<SceneSplit> scenesToDestroy = new List<SceneSplit>();


            SceneSplit splitCheck;
            float count;


            foreach (var scm in sceneCollectionManagers)
            {
                count = scm.loadedScenes.Count;
                for (int i = 0; i < count; i++)
                {
                    splitCheck = scm.loadedScenes[i];
                    if (scm.active)
                    {

                        if (scm.unloadRangeConnectParent == null)
                        {

                            if (Mathf.Abs(splitCheck.posX + splitCheck.xDeloadLimit - scm.xPos) > (int)scm.deloadingRange.x
                                || Mathf.Abs(splitCheck.posY + splitCheck.yDeloadLimit - scm.yPos) > (int)scm.deloadingRange.y
                                || Mathf.Abs(splitCheck.posZ + splitCheck.zDeloadLimit - scm.zPos) > (int)scm.deloadingRange.z)
                                if (splitCheck.sceneGo != null)
                                {
                                    //Debug.Log(sceneCollectionManager.name + " " + sceneCollectionManager.useLoadingRangeMin + " " + item.sceneGo);
                                    scenesToDestroy.Add(splitCheck);
                                }



                            if (scm.useLoadingRangeMin && !scm.useUnloadRangeConnect)
                                if (Mathf.Abs(splitCheck.posX + splitCheck.xDeloadLimit - scm.xPos) <= scm.loadingRangeMin.x &&
                                    Mathf.Abs(splitCheck.posY + splitCheck.yDeloadLimit - scm.yPos) <= scm.loadingRangeMin.y &&
                                    Mathf.Abs(splitCheck.posZ + splitCheck.zDeloadLimit - scm.zPos) <= scm.loadingRangeMin.z)
                                    if (splitCheck.sceneGo != null)
                                    {


                                        scenesToDestroy.Add(splitCheck);

                                    }
                        }

                    }
                    else
                    {
                        scenesToDestroy.Add(splitCheck);
                    }


                }
            }

            UnloadScenes(scenesToDestroy);
            scenesToDestroy.Clear();


            Streamer.UnloadAssets(this);

        }

        private void UnloadScenesSync(SceneSplit item)
        {

            if (item.sceneGo != null)
            {
                Terrain childTerrain = item.sceneGo.GetComponentInChildren<Terrain>();
                if (childTerrain)
                {
                    GameObject childTerrainGO = childTerrain.gameObject;

                    Destroy(childTerrain);
                    childTerrain = null;
                    Destroy(childTerrainGO);
                    childTerrainGO = null;

                }
            }

            item.loaded = false;
            item.loadingFinished = false;
            item.sceneCollectionManager.loadedScenes.Remove(item);
            item.sceneGo = null;



            loadingManager.UnloadSceneAsync(item.scene);

        }

        private void UnloadScenes(List<SceneSplit> scenesToDestroy)
        {
            foreach (var item in scenesToDestroy)
            {
                //Debug.Log("UnloadScenes " + item.sceneName);



                //if (item.sceneGo != null)
                //{
                //    Terrain childTerrain = item.sceneGo.GetComponentInChildren<Terrain>();
                //    if (childTerrain)
                //    {
                //        GameObject childTerrainGO = childTerrain.gameObject;

                //        Destroy(childTerrain);
                //        childTerrain = null;
                //        Destroy(childTerrainGO);
                //        childTerrainGO = null;

                //    }
                //}

                item.loaded = false;
                item.loadingFinished = false;
                item.sceneCollectionManager.loadedScenes.Remove(item);
                item.sceneGo = null;
#if AT_STREAMER2
                tilesLoaded--; 
#endif

                loadingManager.UnloadSceneAsync(item.scene);






            }



            if (terrainNeighbours)
                terrainNeighbours.CreateNeighbours();
        }

        /// <summary>
        /// Unloads all tiles of streamer
        /// </summary>
        public void UnloadAllScenes()
        {

            Debug.Log("UnloadAllScenes start");

            foreach (var sceneCollectionManager in sceneCollectionManagers)
            {

                foreach (var item in sceneCollectionManager.scenesArray)
                {

                    if (item.Value.sceneGo != null)
                    {
                        try
                        {
                            loadingManager.UnloadSceneAsync(item.Value.scene);
                        }
                        catch (System.Exception ex)
                        {
                            Debug.Log(item.Value.sceneName);
                            Debug.Log(item.Value.sceneGo.name);
                            Debug.Log(item.Value.sceneGo.scene.name);
                            Debug.LogError(ex.Message);
                        }



                    }



                    item.Value.loaded = false;
                    item.Value.loadingFinished = false;
                    item.Value.sceneGo = null;
                }
                sceneCollectionManager.loadedScenes.Clear();
            }


            if (terrainNeighbours) terrainNeighbours.CreateNeighbours();

            Streamer.UnloadAssets(this);
            Debug.Log("UnloadAllScenes finish");

        }

        public void UnLoadAllScenesImmediately(Action act=null,Action<float> progress=null)
        {
            StartCoroutine(UnLoadAllScenesImmediatelyAsync(act, progress));
        }
        private IEnumerator UnLoadAllScenesImmediatelyAsync(Action act=null,Action<float> progress=null)
        {
            List<Scene> unLoadList = new List<Scene>();

            foreach (var i in sceneCollectionManagers)
            {
                foreach (var item in i.scenesArray)
                {
                    if (item.Value.loaded)
                    {
                        // 标记以卸载
                        item.Value.loaded = false;
                        item.Value.loadingFinished = false;
                        item.Value.sceneGo = null;
                        unLoadList.Add(item.Value.scene);
                    }
                }

                i.loadedScenes.Clear();
            }


            if (unLoadList.Count != 0)
            {
                for (int i = 0; i < unLoadList.Count; i++)
                {
                    
                    yield return SceneManager.UnloadSceneAsync(unLoadList[i]);
                        //yield return new WaitForSeconds(1); // 假装一下等很久
                        progress?.Invoke((float)i/unLoadList.Count);
                }
            }
            
            if (terrainNeighbours) terrainNeighbours.CreateNeighbours();
            Streamer.UnloadAssets(this);
            unLoadList.Clear();
            yield return Resources.UnloadUnusedAssets();
            
            progress?.Invoke(1);
            act?.Invoke();


        }


        public static void UnloadAssets(Streamer streamer)
        {
            if (Streamer.canUnload)
            {

                Streamer.canUnload = false;

                streamer.StartCoroutine(streamer.UnloadAssetsWait());
            }
            else
                unloadNext = true;
        }

        public IEnumerator UnloadAssetsWait()
        {
            do
            {
                unloadNext = false;
                //Debug.Log("Resources.UnloadUnusedAssets");
                Resources.UnloadUnusedAssets();
                yield return new WaitForSeconds(waitTillNextUnload);

            } while (unloadNext);

            canUnload = true;
        }


        #endregion

        //// called first
        //void OnEnable()
        //{

        //    SceneManager.sceneLoaded += OnSceneLoaded;
        //}


        //void OnDisable()
        //{
        //    SceneManager.sceneLoaded -= OnSceneLoaded;
        //}

        void OnDrawGizmosSelected()
        {
            if (sceneCollectionManagers == null)
                return;
            Vector3 size = Vector3.zero;
            Vector3 position = Vector3.zero;
            Vector3Int loadingRange = Vector3Int.zero;
            foreach (var scm in sceneCollectionManagers)
            {

                if (scm && scm.showDebug && scm.active)
                {

                    Gizmos.color = scm.color;
                    size.x = scm.xSize == 0 ? 2 : scm.xSize;
                    size.y = scm.ySize == 0 ? 2 : scm.ySize;
                    size.z = scm.zSize == 0 ? 2 : scm.zSize;

                    loadingRange.x = !scm.xSplitIs ? 0 : scm.loadingRange.x;
                    loadingRange.y = !scm.ySplitIs ? 0 : scm.loadingRange.y;
                    loadingRange.z = !scm.zSplitIs ? 0 : scm.loadingRange.z;


                    for (int x = -(int)loadingRange.x + scm.xPos; x <= (int)loadingRange.x + scm.xPos; x++)
                    {
                        for (int y = -(int)loadingRange.y + scm.yPos; y <= (int)loadingRange.y + scm.yPos; y++)
                        {
                            for (int z = -(int)loadingRange.z + scm.zPos; z <= (int)loadingRange.z + scm.zPos; z++)
                            {

                                if (scm.useLoadingRangeMin && x - scm.xPos >= -scm.loadingRangeMin.x && x - scm.xPos <= scm.loadingRangeMin.x &&
                                    y - scm.yPos >= -scm.loadingRangeMin.y && y - scm.yPos <= scm.loadingRangeMin.y &&
                                    z - scm.zPos >= -scm.loadingRangeMin.z && z - scm.zPos <= scm.loadingRangeMin.z)
                                {

                                    continue;

                                }
                                else
                                {
                                    if (Application.isPlaying)
                                    {
                                        position.x = x * size.x;
                                        position.y = y * size.y;
                                        position.z = z * size.z;
                                    }
                                    else
                                    {
                                        position.x = (x - scm.xPos) * size.x;
                                        position.y = (y - scm.yPos) * size.y;
                                        position.z = (z - scm.zPos) * size.z;
                                    }



                                    Gizmos.DrawWireCube(position + size * 0.5f + currentMove, size);
                                }

                            }
                        }
                    }

                    Gizmos.color = Color.green;

                    if (Application.isPlaying)
                        Gizmos.DrawWireCube(new Vector3(scm.xPos * size.x, scm.yPos * size.y, scm.zPos * size.z) + size * 0.5f + currentMove, size);
                    else
                        Gizmos.DrawWireCube(size * 0.5f, size);


                }
            }
        }
#if AT_STREAMER2
        public override int GetTilesToLoad() 
        {
            return tilesToLoad;
        }

        public override int GetTilesLoaded()
        {
            return tilesLoaded;
        }

        public override float GetLoadingProgress()
        {
            return (tilesToLoad > 0) ? tilesLoaded / (float)tilesToLoad : 1;
        }
#endif
    }
}